package edu.whu.rose.clock.ssql.graphquery;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;

import com.sleepycat.je.DatabaseException;

import edu.whu.rose.clock.setup.IndexEnvironment;
import edu.whu.rose.clock.ssql.graph.SubGraphDB;
import edu.whu.rose.clock.ssql.graphindex.GraphIndexDB;
import edu.whu.rose.clock.ssql.graphindex.GraphIndexItem;
import edu.whu.rose.clock.ssql.index.EdgeIndexDB;
import edu.whu.rose.clock.ssql.query.EntityQuery;
import edu.whu.rose.clock.ssql.query.Query;
import edu.whu.rose.clock.ssql.query.QueryArrayList;
import edu.whu.rose.clock.ssql.query.RelationshipQuery;
import edu.whu.rose.clock.ssql.util.DoubleSequentialIntMap;
import edu.whu.rose.clock.ssql.util.Merger;
import edu.whu.rose.clock.ssql.util.Path;
import edu.whu.rose.clock.ssql.util.SequentialIntArray;

public class GraphQueryManager {

	private GraphIndexDB gidb;
	private EdgeIndexDB eidb;
	private SubGraphDB sgdb;
	
	private QueryArrayList queries;
	private ArrayList<String> relationships;
	
	public GraphQueryManager(GraphIndexDB gidb, EdgeIndexDB eidb) {
		this.gidb = gidb;
		this.eidb = eidb;
		
		sgdb = new SubGraphDB();
		if (!(sgdb.initEnv() & sgdb.openDB())) {
			System.exit(1);
		}
	}
	
	public static void main(String[] args) {
		IndexEnvironment ie1 = new IndexEnvironment("dbEnv_graphindex");
		ie1.openEnv();
		IndexEnvironment ie2 = new IndexEnvironment("F:/edge index db env");
		ie2.openEnv();
		GraphIndexDB gidb = new GraphIndexDB(ie1.getEnvironment());
		EdgeIndexDB eidb = new EdgeIndexDB(ie2.getEnvironment());
		GraphQueryManager qm = new GraphQueryManager(gidb, eidb);
		qm.init();
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		GraphQueryAnswer result = null;
		try {
			while (true) {
				System.out.println("Please input query:");
				String statement = br.readLine().trim();
				if (!statement.equals("exit")) {
					Date startTime = new Date();
					result = qm.naiveGraphQuery(statement);
					Date endTime = new Date();
					if (result != null) {
						qm.showResult(result);
						System.out.println("Time comsumed: " + (endTime.getTime() - startTime.getTime()) + " ms");
						System.out.println("Number of results: " + result.getSize());
					}
					else {
						System.out.println("Sorry, no result is found.");
					}
				}
				else {
					break;
				}
			}
		}
		catch (DatabaseException ex) {
			ex.printStackTrace();
		}
		catch (IOException ex) {
			ex.printStackTrace();
		}
		qm.close();
		ie1.closeEnv();
		ie2.closeEnv();
	}
	
	public void init() {
		if (!gidb.openDB() || !eidb.openDB()) {
			System.exit(1);
		}
	}
	
	public void close() {
		sgdb.closeDB();
		gidb.closeDB();
		eidb.closeDB();
	}
	
//	public ArrayList<String> naiveQueryProcess(String statement) {
//		this.naiveQueryParse(statement);
//		
//		String leftType = null;
//		Object left = null;
//		
//		ListIterator<Query> li = queries.listIterator();
//		while (li.hasNext()) {
//			Query query = li.next();
//			if (query.getType().equals("entity")) {
//				((EntityQuery)query).setResult(this.naiveGraphQuery(query.getStatement()));
//				if (((EntityQuery)query).getResult() == null) {
//					return null;
//				}
//				if (leftType == null) {
//					leftType = "e";
//					left = ((EntityQuery)query).getResult();
//				}
//				else if (leftType.equals("r")) {
//					left = Merger.rightUnion((DoubleSequentialIntMap)left, ((EntityQuery)query).getResult());
//				}
//				else if (leftType.equals("p")) {
//					left = Merger.rightUnion((Path)left, ((EntityQuery)query).getResult());
//				}
//			}
//			else if (query.getType().equals("relationship")) {
//				((RelationshipQuery)query).setResult(this.naiveEdgeQuery(query.getStatement()));
//				if (((RelationshipQuery)query).getResult() == null) {
//					return null;
//				}
//				if (leftType == null) {
//					leftType = "r";
//					left = ((RelationshipQuery)query).getResult();
//				}
//				else if (leftType.equals("e")) {
//					leftType = "r";
//					left = Merger.leftUnion((int[])left, ((RelationshipQuery)query).getResult());
//				}
//				else if (leftType.equals("r")) {
//					leftType = "p";
//					left = Merger.midUnion((DoubleSequentialIntMap)left, ((RelationshipQuery)query).getResult());
//				}
//				else if (leftType.equals("p")) {
//					left = Merger.midUnion((Path)left, ((RelationshipQuery)query).getResult());
//				}
//			}
//			else {
//				System.out.println("Error: wrong query type \"" + query.getType() + "\"");
//			}
//		}
//		
//		ArrayList<String> result = new ArrayList<String>();
//		if (leftType.equals("p")) {
//			result = ((Path)left).output();
//		}
//		else if (leftType.equals("r")) {
//			result = ((DoubleSequentialIntMap)left).output();
//		}
//		else if (leftType.equals("e")) {
//			for (int i = 0; i < ((int[])left).length; i++) {
//				result.add("" + ((int[])left)[i]);
//			}
//		}
//		else {
//			return null;
//		}
//		
//		return result;
//	}
	
	private void naiveQueryParse(String statement) {
		this.queries = new QueryArrayList();
		this.relationships = new ArrayList<String>();
		int numOfRelationship = 0;
		int numOfEntity = 0;
		if (statement != null) {
			String query = statement.trim();
			for (int left = query.indexOf("["); left != -1; left = query.indexOf("[")) {
				if (left > 0) {
					String str = query.substring(0, left).trim();
					queries.add(new RelationshipQuery(str));
					relationships.add(str);
					numOfRelationship++;
				}
				int right = query.indexOf("]");
				if (right != -1) {
					queries.add(new EntityQuery(query.substring(left + 1, right).trim()));
					numOfEntity++;
				}
				else {
					System.out.println("Error: parsing query statement \"" + statement + "\"");
				}
				query = query.substring(right + 1).trim();
			}
			if (query.length() > 0) {
				queries.add(new RelationshipQuery(query));
				relationships.add(query);
				numOfRelationship++;
			}
			this.queries.setNumOfEntity(numOfEntity);
			this.queries.setNumOfRelationship(numOfRelationship);
		}
	}
	
	public GraphQueryAnswer naiveGraphQuery(String statement) throws DatabaseException {
		String[] segments = statement.split(",");
		GraphIndexItem[] sequences = new GraphIndexItem[segments.length];
		for (int i = 0; i < segments.length; i++) {
			String segment = segments[i].trim();
			if (segment.length() > 0) {
				if (segment.startsWith("(") && segment.endsWith(")")) {
					int pos = segment.indexOf(":");
					if (pos != -1) {
						String attribute = segment.substring(1, pos).trim().toLowerCase();
						String[] keywords = segment.substring(pos + 1, segment.length() - 1).trim().split(" ");
						for (int j = 0; j < keywords.length; j++) {
							sequences[i] = gidb.search(keywords[j].toLowerCase() + "@@" + attribute);;
						}
					}
					else {
						System.out.println("Syntax Error: wrong predicate \"" + segment + "\"");
						return null;
					}
				}
				else {
					sequences[i] = gidb.search(segment.toLowerCase());
				}
			}
			else {
				System.out.println("Syntax Error: wrong statement \"" + statement + "\"");
				return null;
			}
		}
		
		return this.unionAnswers(sequences);
	}
	
	private GraphQueryAnswer unionAnswers(GraphIndexItem[] answers) throws DatabaseException {
		Date start = new Date();
		HashMap<Integer, int[][][]> nums = new HashMap<Integer, int[][][]>();
		
		if (answers[0] != null) {
			for (int i = 0; i < answers[0].getSize(); i++) {
				boolean match = true;
				int[][][] record = new int[2][answers.length][];
				record[0][0] = new int[]{answers[0].getTf(i)};
				record[1][0] = answers[0].getVertexGroup(i);
				for (int j = 1; j < answers.length; j++) {
					if (answers[j] == null) {
						continue;
					}
					if (match) {
						match = false;
						
						int low = 0;
						int mid = 0;
						int top = answers[j].getSize() - 1;
						
						while (low <= top) {
							mid = (low + top) / 2;
							if (answers[0].getGraph(i) < answers[j].getGraph(mid)) {
								top = mid - 1;
							}
							else if (answers[0].getGraph(i) > answers[j].getGraph(mid)) {
								low = mid + 1;
							}
							else {
								match = true;
								record[0][j] = new int[]{answers[j].getTf(mid)};
								record[1][j] = answers[j].getVertexGroup(mid);
								break;
							}
						}
					}
					else {
						break;
					}
				}
				if (match) {
					nums.put(new Integer(answers[0].getGraph(i)), record);
				}
			}
			
			int size = nums.size();
			GraphQueryAnswer gqa = new GraphQueryAnswer(size);
			Iterator<Integer> it = nums.keySet().iterator();
			while (it.hasNext()) {
				Integer key = it.next();
				int graph = key.intValue();
				int[][][] temp = nums.get(key);
				double score = 0.0;
				SequentialIntArray sia = new SequentialIntArray();
				for (int j = 0; j < answers.length; j++) {
					if (answers[j] == null) {
						continue;
					}
					int tf = temp[0][j][0];
					double score1 = (double)tf / (double)sgdb.getSGKN(graph);
					double score2 = Math.log((double)2334 / (double)answers[j].getSize());
					score += score1 * score2;
					int[] vertexes = temp[1][j];
					for (int k = 0; k < vertexes.length; k++) {
						sia.insert(vertexes[k]);
					}
				}
				gqa.add(score, graph, sia.getArray());
			}
			Date end = new Date();
			System.out.println("Time comsumed for unioning graph answers: " + (end.getTime() - start.getTime()) + "ms");
			return gqa;
		}
		
		return null;
	}
	
	private GraphQueryAnswer procGraphIndexItem(GraphIndexItem gii) throws DatabaseException {
		int size = gii.getSize();
		GraphQueryAnswer gqa = new GraphQueryAnswer(size);
		double score2 = Math.log((double)2334 / (double)size);
		for (int i = 0; i < size; i++) {
			int tf = gii.getTf(i);
			double score1 = (double)tf / (double)sgdb.getSGKN(gii.getGraph(i));
			double score = score1 * score2;
			gqa.add(score, gii.getGraph(i), gii.getVertexGroup(i));
		}
		return gqa;
	}
	
	public DoubleSequentialIntMap naiveEdgeQuery(String statement) {
		DoubleSequentialIntMap result = new DoubleSequentialIntMap();
		String[] pairs = eidb.search(statement);
		if (pairs != null) {
			for (int i = 0; i < pairs.length; i++) {
				String pair = pairs[i].trim();
				if (pair.length() > 0) {
					int pos = pair.indexOf("/");
					if (pos != -1) {
						String row = pair.substring(0, pos).trim();
						String column = pair.substring(pos + 1).trim();
						try {
							result.doubleInsert(new Integer(row).intValue() + 1, new Integer(column).intValue() + 1);
						}
						catch (NumberFormatException ex) {
							ex.printStackTrace();
							System.err.println("Error: wrong edge");
						}
					}
					else {
						System.err.println("Error: wrong edge");
					}
				}
				else {
					System.err.println("Error: empty edge");
				}
			}
		}
		return result;
	}
	
	public void showResult(GraphQueryAnswer gqa) {
		for (int i = gqa.getSize() - 1; i >= 0; i--) {
			System.out.print(gqa.getScore(i) + ":   ");
			System.out.print(gqa.getGraph(i) + " (");
			for (int j = 0; j < gqa.getVertexGroupSize(i); j++) {
				System.out.print(gqa.getVertex(i, j) + " ");
			}
			System.out.println("), ");
		}
	}
	
//	public ArrayList<String> showFullResult(ArrayList<String> result) {
//		ArrayList<String> newResult = new ArrayList<String>();
//		ListIterator<String> li = result.listIterator();
//		while (li.hasNext()) {
//			String path = li.next().trim();
//			String newPath = "";
//			int num = 0;
//			for (int left = path.indexOf("-->"); left != -1; left = path.indexOf("-->")) {
//				if (left > 0) {
//					String str = path.substring(0, left).trim();
//					try {
//						int vertex = new Integer(str).intValue();
//						newPath += vertex + " " + gdb.get(vertex);
//					}
//					catch (UnsupportedEncodingException ex) {
//						ex.printStackTrace();
//						System.err.println(str);
//					}
//					catch (DatabaseException ex) {
//						ex.printStackTrace();
//						System.err.println(str);
//					}
//				}
//				else {
//					System.err.println("somethingwrong......");
//				}
//				String relationship = relationships.get(num);
//				num++;
//				newPath += "--" + relationship + "-->\n";
//				path = path.substring(left + 3).trim();
//			}
//			if (path.length() > 0) {
//				try {
//					int vertex = new Integer(path).intValue();
//					newPath += vertex + " " + gdb.get(vertex);
//				}
//				catch (UnsupportedEncodingException ex) {
//					ex.printStackTrace();
//					System.err.println(path);
//				}
//				catch (DatabaseException ex) {
//					ex.printStackTrace();
//					System.err.println(path);
//				}
//			}
//			else {
//				System.err.println("somethingwrong......");
//			}
//			newResult.add(newPath);
//		}
//		return newResult;
//	}

}
